/**
 * OSPF.java
 *
 * @author Philip Kwok
 * @author Myongsu Choe
 * @author Hagen Boehm
 */


package SSF.OS.OSPFv2;


import java.io.*;
import java.util.*;
import java.lang.Boolean;
import com.renesys.raceway.SSF.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import SSF.Util.Random.RandomStream;
import SSF.OS.OSPFv2.test.ConfigUpdateMessage;
import SSF.OS.*;
import SSF.OS.BGP4.Util.AS_descriptor;


// ========================= class SSF.OS.OSPFv2.OSPF ======================= //
/**
 * The OSPFv2 interior gateway protocol (IGP).
 */
public class OSPF extends ProtocolSession implements FIBChangeListener {

  // ......................... constants ........................... //
  public static final int PROTOOSPF = 89;

  // - - - - - - - - - - architectural constants - - - - - - - - - - //
  /** The maximum time between distinct originations of any particular self-
   *  originated LSA. */
  public static final long LS_REFRESH_TIME = 1800 * SSF.Net.Net.seconds(1.0);
  /** The minimum time between distinct originations of any particular LSA. */
  public static final long MIN_LS_ITVL = 5 * SSF.Net.Net.seconds(1.0);
  /** The min time that must elapse between reception of new LSA instances 
   *  during flooding. */
  public static final long MIN_LS_ARRIVAL = 1 * SSF.Net.Net.seconds(1.0);
  /** The max of age an LSA can attain. */
  public static final int MAX_AGE = 3600;
  /** The max time dispersion that can occur, as an LSA is flooded throughout
   *  the AS. */
  public static final int MAX_AGE_DIFF = 900;
  /** The metric value indicating that the destination described by an LSA is
   *  unreachable */
  public static final int LS_INFINITY = 16777215;
  /** The Destination ID that indicates the default route. */
  public static final String DEFAULT_DEST = "0.0.0.0";
  /** The value used for LS Sequence Number when originating the first instance
   *  of any LSA. */
  public static final int INIT_SEQUENCE_NUM = -2147483647;
  /** The maximum value that LS Sequence Number can attain. */
  public static final int MAX_SEQUENCE_NUM = 2147483647;
  /** The Maximum Transfer Unit supported on all Interfaces. */
  public static final int MTU = 1500;

  // - - - - - - - - - - link types - - - - - - - - - - // 
  /** Indicates a point-to-point connection to another router. */
  public static final byte POINT_TO_POINT = 1;
  /** Indicates a connection to a transit network. */
  public static final byte TO_TRANSIT_NET = 2;
  /** Indicates a connection to a stub network. */
  public static final byte TO_STUB_NET    = 3;
  /** Indicates a Virtual link to another area border router. */
  public static final byte VIRTUAL_LINK   = 4;
  /** Indicates a connection to an AS boundary router. */
  // public static final byte TO_AS_BOUNDARY = 24;

  // - - - - - - - - - - path types - - - - - - - - - - //
  /** Indicates a path-type of intra-area. */
  public static final byte INTRA_AREA = 1;
  /** Indicates a path-type of inter-area. */
  public static final byte INTER_AREA = 2;
  /** Indicates a path-type of type-1-external. */
  public static final byte TYPE_1_EXTERNAL = 3;
  /** Indicates a path-type of type-2-external. */
  public static final byte TYPE_2_EXTERNAL = 4;

  // - - - - - - - - multicast addresses - - - - - - - - //
  /** The int value of the multicast address for ALLSPFRouters (All Shortest
   *  Path First Routers). The real IP address is 224.0.0.5, but here we will
   *  use Integer.MAX_VALUE - 10. */
  public static final int ASPF_MCAST_ADDR  = Integer.MAX_VALUE - 10;
  public static final int DESIG_MCAST_ADDR = Integer.MAX_VALUE - 11;

  // ........................ member data .......................... //

  /** A reference to the top-level Net. */
  public static Net topnet;

  /** The local router. */
  public Router rtr;

  /** The NHI address prefix of the local router. */
  public String nh;

  /** The identity of this router.  It is chosen as the smallest of the IP of
   *  all its interfaces (in A.B.C.D/bits form). */
  public int rtrid;

  /** Indicates if this is an internal Router. */
  public boolean internal_rtr;

  /** Indicates if this is an area border router. */
  public boolean area_border_rtr;

  /** Indicates if this is a backbone router. */
  public boolean backbone_rtr;

  /** Indicates if this is an AS boundary router. */
  public boolean as_boundary_rtr;

  /** The NHI address prefix of the AS to which this router belongs. */
  public String as_nh;

  /** The number of the AS to which this router belongs. */
  public int as_num = AS_descriptor.NO_AS;

  /** A HashMap which lists all area data structures of this OSPF router.
   *  Its keys are the numbers of the areas to which this router is attached to,
   *  the values contains the corresponding area data structures */
  public HashMap areaStructs;

  /** The OSPFv2 Routing Table of this ProtocolSession. */
  public HashMapRoutingTable routingTable;

  /** The forwarding table, kept in the IP protocol session, which is the
   *  "live" table used for lookups when this router forwards packets. */
  public RoutingTable ipTable;

  /** A reference to the underlying IP protocol session. */
  IP ip;

  /** boolean value indicating that the underlying IP protocol session was already opened. */
  private boolean ipIsOpened;

  /** boolean value indicating that the OSPF configuration has changed. */
  private boolean configChanged = false;

  /** String indicating the routers ospf type. There is: Internal, Backbone, Area Border and
   *  AS Boundary which is not determined yet.
   */
  private String routerType;

  /** The show options from the DML-config-file will be recorded here */
  public boolean[] debugOptions = new boolean[11];

  /** A HashMap for dynamic Configuration of this OSPF session. */
  public HashMap dynConfKeys;

  /** A Timer aging the LSAs upon expiration */
  public AgingTimer agingTimer;

  /** The duration of the AgingTimer */
  public long agingInterval;

  /** boolean value indicating that the session is stopped and no more Packets will be received. */
  private boolean isStopped;

  /** RandomStream to produce random values where it's needed due to RFC 2328 and to add dynamic
   *  behaviour to the protocol. */
  public RandomStream randomStream;

  /** The Routing Table Calculation Control Timer. It manages shortest-path-tree calculation and 
   *  OSPF/IP table updates. 
   */
  public RTCControlTimer rtcControlTimer;

  /** long value specifying how long OSPF waits between receiving a topology change and starting an
   *  SPF calculation.
   */
  public long spfDelay;

  /** long value specifying how long OSPF waits between two consecutive SPF calculations */
  public long spfHoldTime;

  /** long value indicating the time at which LSAs in the "Neighbor.pacingFloodList" are paced in
   *  between updates. 
   */
  public long pacingFloodTime;

  // ----------------------------- OSPF.config ---------------------------- //
  /** Configure the ospf protocol parameters. Used at the start and in a run for
   *  reconfiguration purposes. Supported DML attributes are:
   *  <PRE>
   *  ospf [
   *         # This parameter sets the time interval at which the link state database 
   *         # is aged when it is kept in memory.
   *         # optional! default value is: 4
   *         age_itvl        %I
   *
   *         # The following attributes configure some ospf specific timers: 
   *         # delay time in seconds between topology change and spf calculation. The default value is 5.
   *         spf_delay         %I<1:(0...65535)
   *         # delay time in seconds between two consecutive spf calculations. The default value is 10.
   *         spf_hold_time     %I<1:(0...65535)
   *         # delay time in seconds(!) at which LSAs in the flooding queue are paced in between updates.
   *         # The default value is 0.033.
   *         pacing_flood_time %F<1:(0.005...0.1)
   *
   *         # All show* parameters toggle certain debug output.
   *         # show* is optional! default value is: false
   *   	     # Link state database output
   *         show_lsdb               %S
   *         # Output the forwarding table
   *         show_fwd_table          %S
   *         # Output the shortest paths tree
   *         show_shortest_path_tree %S
   *         # Output information about hello packets
   *         show_hello_pkt  	     %S
   *         # Output information about database description packets
   *         show_dd_pkt     	     %S
   *         # Output information about the flooding procedure
   *         show_flooding   	     %S
   *         # Output information about area configuration
   *         show_area       	     %S
   *         # Output information about the neighbor states
   *         show_neighbors  	     %S # no output at the moment
   *         # Output information about the interface configuration
   *         show_interfaces  	     %S
   *         # Output information about the OSPF configuration
   *         show_config     	     %S
   *         # Enable all output at once
   *         show_all        	     %S
   *
   *	     # Each OSPF router belongs to at least one area.
   *         area %T:.schemas.area  
   *       ]
   *
   * @param cfg contains the values for configurable ospf protocol parameters
   * @exception configException  thrown if any of the calls to <code>findSingle</code> and
   *                             <code>find</code> throw such exception or if a misconfiguration 
   *                             is determined. 
   */
  public void config(Configuration cfg) throws configException {

    String isUpdate = (String)cfg.findSingle("is_update");
    boolean isUpd = (isUpdate != null) && (isUpdate.equals("true"));

    // A HashMap for dynamic Configuration of this OSPF session in case of a configuration
    // update.
    HashMap dynConfKeys = new HashMap();
    // An Enumeration to keep the individual configurations for the areas
    Enumeration areaConfData;
    // current Area Configuration
    Configuration nextConfig;
    // the Area ID of the Area current to configure. 
    Integer nextAreaID;

    for(Enumeration k = ((dmlConfig)cfg).keys();k.hasMoreElements();) {
      String ak = (String)k.nextElement();
      dynConfKeys.put(ak, new Integer(1));
    }
    // Configure the ospf router attributes.
    try {
      // Configure the timer values.
      if (dynConfKeys.containsKey("age_itvl")) {
	agingInterval = Long.parseLong((String)cfg.findSingle("age_itvl"));
        agingInterval *= SSF.Net.Net.seconds(1.0);
        configChanged = true;
      }
      if (dynConfKeys.containsKey("spf_delay")) {
	spfDelay = Long.parseLong((String)cfg.findSingle("spf_delay"));
	spfDelay *= SSF.Net.Net.seconds(1.0);
        configChanged = true;
      }
      if (dynConfKeys.containsKey("spf_hold_time")) {
        spfHoldTime = Long.parseLong((String)cfg.findSingle("spf_hold_time"));
        spfHoldTime *= SSF.Net.Net.seconds(1.0);
        configChanged = true;
      }
      if (dynConfKeys.containsKey("pacing_flood_time")) {
	  pacingFloodTime = SSF.Net.Net.seconds(Float.parseFloat((String)cfg.findSingle("pacing_flood_time")));
        if (pacingFloodTime < SSF.Net.Net.seconds(0.005) || pacingFloodTime > SSF.Net.Net.seconds(0.1)) 
           throw new configException("pacing_flood_time not in range! (must be between 0.005 and 0.1 seconds)");
        configChanged = true;
      }
      // Configure the debug options.
      if (dynConfKeys.containsKey("show_lsdb")) {
        debugOptions[0] = (new Boolean((String)cfg.findSingle("show_lsdb"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_fwd_table")) {
        debugOptions[1] = (new Boolean((String)cfg.findSingle("show_fwd_table"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_hello_pkt")) {
        debugOptions[2] = (new Boolean((String)cfg.findSingle("show_hello_pkt"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_dd_pkt")) {
        debugOptions[3] = (new Boolean((String)cfg.findSingle("show_dd_pkt"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_flooding")) {
        debugOptions[4] = (new Boolean((String)cfg.findSingle("show_flooding"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_area")) {
        debugOptions[5] = (new Boolean((String)cfg.findSingle("show_area"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_neighbors")) {
        debugOptions[6] = (new Boolean((String)cfg.findSingle("show_neighbors"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_interfaces")) {
        debugOptions[7] = (new Boolean((String)cfg.findSingle("show_interfaces"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_config")) {
        debugOptions[8] = (new Boolean((String)cfg.findSingle("show_config"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_all")) {
        debugOptions[9] = (new Boolean((String)cfg.findSingle("show_all"))).booleanValue();
        configChanged = true;
      }
      if (dynConfKeys.containsKey("show_shortest_path_tree")) {
	debugOptions[10] = (new Boolean((String)cfg.findSingle("show_shortest_path_tree"))).booleanValue();
        configChanged = true;
      }
    } catch (configException cex) {
      printGeneralError("configuring one of the router's attributes", cex);
    }

    // Configure Areas and Interfaces.
    if(!isUpd) {
    // Only executed when OSPF is configured for the first time.
      super.config(cfg);
      areaStructs = new HashMap();
      // set a few member fields that may get referenced before init is called
      rtr    = (Router)inGraph(); // the local router
      topnet = rtr.net;  // set a reference to the top-level Net
      nh     = rtr.nhi;
      randomStream = Net.accessRandomStream(this, "ospf@"+nh); // initialize randomStream

      try {
        if (dynConfKeys.containsKey("area")) {
	  // Area was found explicit configured for this router.
          areaConfData = (Enumeration)cfg.find("area");
        } else {
	  // Have to find a default configuration of at least one Area.
          // search for a default area configurationget in the higher-level Net's CIDR blocks
	  // until you find one or reach the top level.
          cidrBlock blk = rtr.defined_in_network();
          while ((blk.nhi_parent() != null) && 
                 (blk.networkConfiguration().find("default_area") == null)) {
            blk = blk.nhi_parent();
          }
          if (blk.networkConfiguration().find("default_area") == null) 
            throw new configException("No Area configured!");
          areaConfData = (Enumeration)blk.networkConfiguration().find("default_area");
	}
        while (areaConfData.hasMoreElements()) {
	  // find a new Area configuration
	  nextConfig = (Configuration)areaConfData.nextElement();
          if (nextConfig.findSingle("id") != null) {
	    // Area has an ID next to configure
            nextAreaID = new Integer((String)nextConfig.findSingle("id"));
            if (areaStructs.containsKey(nextAreaID))
              throw new configException("Area "+nextAreaID.toString()+" can not be configured twice!");
          } else {
            // Area has no ID, so it must be the backbone or a config error has occured.
            nextAreaID = new Integer(0);
            if (areaStructs.containsKey(nextAreaID)) 
              throw new configException("No Area id set!");
          }
          // construct a new Area and add it to the HashMap.
          areaStructs.put(nextAreaID, new AreaData(this, nextConfig));
        }  
      } catch (configException cex) {
	System.err.println("OSPF configuration error: " + cex.getMessage());
	cex.printStackTrace();
        System.exit(-1);
      }
    } else {
    // Only executed when OSPF is configured by a configuration update   
      try {
        if (dynConfKeys.containsKey("area")) {
          areaConfData = (Enumeration) cfg.find("area");
          while (areaConfData.hasMoreElements()) {
            // find a new Area configuration
	    nextConfig = (Configuration)areaConfData.nextElement();
            if (nextConfig.findSingle("id") != null)
              // Area has an ID next to reconfigure
              nextAreaID = new Integer((String)nextConfig.findSingle("id"));
            else 
	      // reconfigure the backbone.
	      nextAreaID = new Integer(0);
	    if (areaStructs.containsKey(nextAreaID))
	      // reconfigure an existing area.
	      ((AreaData)areaStructs.get(nextAreaID)).config(nextConfig);
            else
              // create a new area.
              areaStructs.put(nextAreaID, new AreaData(this, nextConfig));
	  }
        }
      } catch (configException cex) {
        printGeneralError("processing a configuration update", cex);
      }

      // determine the router's ospf type
      try {
        routerType = determineRouterType();
      } catch (configException cex) {
	printGeneralError("determining the router's type", cex);
      }

      // Print Router configuration if necessary.
      printConfiguration();
    }
  }

  // -------------------------- constructor OSPF -------------------------- //
  /**
   * Constructs an OSPF protocol session.
   */
  public OSPF() {
    agingInterval   = SSF.Net.Net.seconds(4.0);
    spfDelay        = SSF.Net.Net.seconds(5.0);
    spfHoldTime     = SSF.Net.Net.seconds(10.0);
    pacingFloodTime = SSF.Net.Net.seconds(0.033);
    routingTable    = new HashMapRoutingTable(this);
    ipIsOpened      = false;
    isStopped       = false;
  }

  // ------------------------------ OSPF.init ----------------------------- //
  /**
   * Initialization routine called by ProtocolGraph after instantiation and when
   * restarting the router after it has been stopped. It finishes the configuration
   * of all areas belonging to the session if necessary, determines router ID and 
   * ospf router type and sets the aging Timer.
   * Information that OSPF needs in the setup stage include:
   * 1. a reference to the IP protocol
   * 2. a reference to the router in which this OSPF sits
   * 3. registration as FIBChangeListener to the IP Forwarding Table
   * 
   */
  public void init() throws ProtocolException {

    if (!ipIsOpened) {
      try {
        ip = (IP)inGraph().SessionForName("ip");
        ipTable = (RoutingTable)ip.getRoutingTable(); // get a reference to the IP Forwarding Table
        ipTable.addFIBChangeListener(this); // Registration of OSPFv2 as FIBChangeListener
        ip.open(this, null);
      } catch (ProtocolException pex) {
        printConfigError("IP", "", pex);
      }
    }

    // Get the AS id to which this router is attached. 
    as_nh  = AS_descriptor.get_as_nh(rtr);
    as_num = AS_descriptor.nh2as(as_nh);

    if (!isStopped) {
      // finish configuration of Areas and Interfaces if this is not a restart and instantiate
      // some timers if necessary.
      try {
        for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
          ((AreaData)areas.next()).configRest();
        }
      } catch (configException cex) {
        System.err.println("OSPF configuration error: " + cex.getMessage());
        cex.printStackTrace();
        System.exit(-1);
      }
      // initiate the routing table calculation control timer.
      rtcControlTimer = new RTCControlTimer(this, spfDelay);
      rtcControlTimer.cancel();
    }

    // set the router id to the smallest IP interface address belonging to the OSPF router.
    rtrid = getRouterID();
    // determine the router's ospf type
    try {
      routerType = determineRouterType();
    } catch(configException cex) {
      printGeneralError("determining the router's type", cex);
    }
    // Print Router configuration if necessary.
    printConfiguration();

    // initialize ospf in Area- and Interface-structures
    for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
      ((AreaData)areas.next()).init();
    }

    // Start the AgingTimer.
    agingTimer = new AgingTimer(this, agingInterval);
    agingTimer.set(agingInterval);

    // Calculate the OSPFv2 routing table.
    calculateRT();
    for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
      ((AreaData)areas.next()).databaseDebugInfo();
    }
    if (debugOptions[9] == true || debugOptions[1] == true) routingTable.print();
    
    if (isStopped) {
      // session is restarting.
      if (debugOptions[9]) {
	printMessage("|OSPF session restarted!");
      }
      isStopped = false;
    }
  }

  // ------------------------------- OSPF.opened --------------------------- //
  /**
   *  Overridden. Only one IP session can confirm successfull configuration.
   */
  public void opened(ProtocolSession myIP) {
    if (myIP.equals(ip)) ipIsOpened = true;
  }

  // ------------------------------- OSPF.stop ----------------------------- //
  /**
   *  Stop this OSPF session. That means stop all Timers, initialize all Data
   *  Structures to Down, 0, null or whatever is appropriate, and reject all
   *  arriving Packets.
   */
  public void stop() {
    isStopped = true;
    if (agingTimer != null) {
      agingTimer.cancel();
      agingTimer = null;
    }
    rtcControlTimer.reset();
    for (Iterator areas = areaStructs.values().iterator(); areas.hasNext(); ) {
	((AreaData)areas.next()).reset();
    }
    if (debugOptions[9]) {
      printMessage("|OSPF session stopped!");
    }
  }

  // ---------------------------- OSPF.getRouterID ------------------------ //
  /** Return the smallest IP interface address belonging to this OSPF router. */
  public int getRouterID() {
    AreaData nextArea;
    Interface nextInterface;
    int smallestIP = Integer.MAX_VALUE;
    for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
      nextArea = (AreaData)areas.next();
      for (Iterator intfs = nextArea.interfaces.values().iterator(); intfs.hasNext(); ) {
	nextInterface = (Interface)intfs.next();
        if (nextInterface.ipaddr < smallestIP) smallestIP = nextInterface.ipaddr;
      }
    }
    return smallestIP;
  }

  // ------------------------------ OSPF.nowsec --------------------------- //
  /**
   * Returns the current simulation time in seconds.
   *
   * @return the current simulation time in seconds
   */
  public double nowsec() {
    return ((double)(inGraph().now()))/((double)Net.frequency);
  }

  // ----------------------------- OSPF.getSimTime ------------------------ //
  /**
   * Get the current simulation time.
   */
  public float getSimTime() {
    return this.inGraph().now()/((float)SSF.Net.Net.seconds(1.0));
  }

  // ---------------------------- OSPF.determineRouterType ------------------------ //
  /**
   * Determine router type by checking the areas. A router is supposed to be an AS Boundary
   * Router if BGP4 is running on it, the router is NOT placed in a stub area and one of its
   * BGP Neighbors sits in another AS. 
   * @exception configException if any of the calls to find() and/or findSingle() throw such an
   *            exception during the AS Boundary check.
   * @return String describing the ospf type of the router.
   */
  private String determineRouterType() throws configException {
    if (areaStructs.isEmpty()) {
      internal_rtr = false; backbone_rtr = false; area_border_rtr = false; as_boundary_rtr = false;
      return ("No Area assigned! Router type could not be determined!");
    }

    Integer backBoneArea = new Integer(0);
    String msg = "";
    NIC nextNIC;
    Host nextPeerHost;
    Configuration protoGraph, peerProtoGraph, protoSession, peerProtoSession;
    String protoName, peerProtoName;
    Enumeration bgpNeighbors = null;

    if (areaStructs.size() == 1) {
      // all directly connected networks belonging to the same area.
      internal_rtr = true;
      msg = "internal ";
    } else internal_rtr = false;
    if (areaStructs.containsKey(backBoneArea)) {
      // the router has a connection to the backbone.
      backbone_rtr = true;
      msg+= "backbone ";
    } else backbone_rtr = false;
    if (areaStructs.size() > 1 && backbone_rtr) {
      // the router attaches to multiple areas.
      area_border_rtr = true;
      msg+= "area border ";
    } else area_border_rtr = false;
    // now determine whether this router is an AS boundary router.
    protoGraph = (Configuration)rtr.hostConfig.findSingle("graph");
    if (protoGraph == null) {
      System.err.println("OSPF configuration error! No 'graph' attribute defined on router " + nh +
                         " (OSPF id " + rtrid + ")");
      System.exit(-1);
    }
    for (Enumeration sessions = protoGraph.find("ProtocolSession"); sessions.hasMoreElements(); ) {
      protoSession = (Configuration)sessions.nextElement();
      protoName = (String)protoSession.findSingle("name");
      if (protoName.compareTo("bgp") == 0) {
	// a bgp session is configured on this router
	bgpNeighbors = protoSession.find("neighbor");
        if (bgpNeighbors != null && bgpNeighbors.hasMoreElements()) {
          // bgp neighbors are configured.
          String neighborASNum;
          while (bgpNeighbors.hasMoreElements()) {
	    neighborASNum = (String)((Configuration)bgpNeighbors.nextElement()).findSingle("as");
            if (neighborASNum != null && as_num != Integer.parseInt(neighborASNum)) {
	      if (internal_rtr) {
		AreaData internalArea = (AreaData)areaStructs.values().iterator().next();
                if (!internalArea.options[6]) {
		  // You can't place AS Boundary routers in stub areas.
		  printError("Area", String.valueOf(internalArea.areaID), "Found AS Boundary router in stub area! Router is NOT determined to be AS Boundary. According to RFC 2328 chapter 3.6.");
		  break;
		}
	      }
              as_boundary_rtr = true;
              msg+= "AS boundary ";
              break;
            }
          }
        } else {
          // bgp neighbors are NOT configured.
          for (Iterator nics = rtr.interfaceNumbers.values().iterator(); nics.hasNext(); ) {
            nextNIC = (NIC)nics.next();
            if (nextNIC.link_hw instanceof ptpLinkLayer) {
	      nextPeerHost = (Host)(((ptpLinkLayer)nextNIC.link_hw).peer(nextNIC).inGraph());
              if (nextPeerHost instanceof Router) {
	        peerProtoGraph = (Configuration)nextPeerHost.hostConfig.findSingle("graph");
                if (peerProtoGraph != null) {
                  for (Enumeration peerSessions = peerProtoGraph.find("ProtocolSession"); 
                       peerSessions.hasMoreElements(); ) {
                    peerProtoSession = (Configuration)peerSessions.nextElement();
                    peerProtoName = (String)peerProtoSession.findSingle("name");
                    if (peerProtoName.compareTo("bgp") == 0 && 
                        as_num != AS_descriptor.nh2as(AS_descriptor.get_as_nh(nextPeerHost))) {
		      // neighboring router runs a bgp session and sits in an other AS.
                      if (internal_rtr) {
		        AreaData internalArea = (AreaData)areaStructs.values().iterator().next();
                        if (!internalArea.options[6]) {
		          // You can't place AS Boundary routers in stub areas.
		          printError("Area", String.valueOf(internalArea.areaID), "Found AS Boundary router in stub area! Router is NOT determined to be AS Boundary. According to RFC 2328 chapter 3.6.");
		          break;
		        }
	              }
                      as_boundary_rtr = true;
                      msg+= "AS boundary ";
                      break;
	            }
	          }
                }
	      }
            }
          }
        }
        break;
      }
    } 
    
    return ("Router type: "+msg+ "router");
  }

  // ------------------------------- OSPF.push ---------------------------- //
  public boolean push(ProtocolMessage message, ProtocolSession fromSession)
                                                     throws ProtocolException {
    if (isStopped) return false;
    try {
      if (message instanceof Packet) {
        IpHeader ipPkt = (IpHeader)message.previous();
        Packet ospfPkt = (Packet)message;
        Integer sourceIP = new Integer(ipPkt.SOURCE_IP);
        Integer destIP = new Integer(ipPkt.DEST_IP);
        AreaData nextArea;
        int recAreaID = -1; 
        // Packets which are not OSPF Packets will be dropped.
        if (ipPkt.PROTOCOL_NO != PROTOOSPF) return false;
        for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
          nextArea = (AreaData)areas.next();
          // Locally originated packets should be dropped.
          if(nextArea.hasInterface(sourceIP)) return false;
          // packet's IP destination address must be the IP of an Interface belonging to this Router.
          if(nextArea.hasInterface(destIP)) {
	    if (recAreaID == -1) recAreaID = nextArea.areaID;
            else throw new ProtocolException("Found an Interface belonging to two Areas!");
          }
        }
        // Check whether Packet was destined for an Interface of this OSPF session
        if (recAreaID == -1) return false; 
        // Check Area ID of the Packet.
        if (recAreaID == ospfPkt.areaNum) {
	  // Packet's Area ID matches the Area ID of the receiving Interface.
	  // Further processing is done now by the Area the receiving Interface belongs to.
	  return ((AreaData)areaStructs.get(new Integer(recAreaID))).receivedPacket(ospfPkt, sourceIP, destIP);
        } else {
	  // area verification has failed! Virtual Links are not considered.
	  if (debugOptions[9] == true || debugOptions[2] == true)
            printMessage("Received Packet with wrong area ID! On " + IP_s.IPtoString(ipPkt.DEST_IP) 
              + " From OSPF Router " + ospfPkt.nh + " Interface " + IP_s.IPtoString(ipPkt.SOURCE_IP)
              + " Packet discarded!");
        }
      } else 
	if (message instanceof ConfigUpdateMessage) {
          Configuration configUpdate = ((ConfigUpdateMessage)message).getConfig();
          if (debugOptions[9] == true || debugOptions[8] == true)
	    printMessage("Received configuration update!");
          config(configUpdate);
        }
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace();
      System.exit(-1);
    }
    return false;
  }

  // --------------------------- OSPF.isSelforiginated ------------------------------- //
  /** Check whether testLSA is an LSA which has been originated by the router itself and 
   *  return true if so, else false.
   */
  public boolean isSelforiginated(LSA testLSA) {
    if (testLSA.lsType == LSA.NETWORK_LSA) {
      // The LSA is a NETWORK_LSA. Compare lsID with the IP addresses of all Interfaces 
      // belonging to this OSPF router.
      AreaData nextArea;
      for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
	nextArea = (AreaData)areas.next();
        if (nextArea.compareIPAddrWith(testLSA.lsID)) return true;
      }
    }
    if (testLSA.advRouter == rtrid) return true;
      // This OSPF has advertised the LSA, so it is selforiginated.
    return false;
  }

  // ------------------------- OSPF.floodExternalLSA ------------------------------- //
  /** Flood an external LSA out of the router's interfaces. */
  public void floodExternalLSA(LSA extLSA) {
      // still to do.
  }

  // ------------------------------ OSPF.calculateRT -------------------------------- //
  /** Recalculate the entire Routing Table. Therefore the current routing table will be 
   *  replaced by a new one calculated from the LSAs currently stored in this router. All 
   *  areas contribute to this recalculation by performing Dijkstra's stage 1 and 2 (see 
   *  RFC 2328 chapter 16.1). While building the new Routing Table, the ip forwarding table
   *  is updated route by route.
   */
  public long calculateRT() {
    HashMapRoutingTable oldRoutingTable;
    AreaData nextArea, removeArea;
    HashMap newRoutes;
    String routeKey;
    Integer routDest;
    NetworkEntry newNetEntry, currentNetEntry;
    RouterEntry nextRoutEntry;
    int x = 0; // number of nodes of spTree in the biggest area.

    // Save the old routing table...
    oldRoutingTable = routingTable;
    routingTable = new HashMapRoutingTable(this);
    // ...and build a new one.
    for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
      // For every area...
      nextArea = (AreaData)areas.next();
      if (x < nextArea.database.routerLSAs.size()) x = nextArea.database.routerLSAs.size();
      // calculate the first stage of Dijkstra according to RFC 2328 chapter 16.1
      newRoutes = nextArea.calculateDijkstraPartI();
      for (Iterator rts = newRoutes.keySet().iterator(); rts.hasNext(); ) {
	routDest = (Integer)rts.next();
        nextRoutEntry = (RouterEntry)newRoutes.get(routDest);
        routingTable.add(nextRoutEntry, routDest, new Integer(nextArea.areaID));
      }
      // calculate the second stage of Dijkstra according to RFC 2328 chapter 16.1
      newRoutes = nextArea.calculateDijkstraPartII();
      for (Iterator stubs = newRoutes.keySet().iterator(); stubs.hasNext(); ) {
	routeKey = (String)stubs.next();
        newNetEntry = (NetworkEntry)newRoutes.get(routeKey);
        currentNetEntry = routingTable.getDest(routeKey);
        if (currentNetEntry == null) {
          routingTable.add(newNetEntry, routeKey);
        } else {
	  if (newNetEntry.cost < currentNetEntry.cost) routingTable.add(newNetEntry, routeKey);
        }
      }
      if (area_border_rtr && !nextArea.options[6] && nextArea.stubDefaultCost > 0) {
        // update a Default-Summary-LSA if necessary (after first or re-configuration).
        String defaultKey = LSA.SUMMARY_NETWORK_LSA + " " + IP_s.IPtoString(0) + " " +
                            IP_s.IPtoString(rtrid);
	LSA defaultLSA = nextArea.getLSA(defaultKey);
        if (defaultLSA == null) {
          defaultLSA = nextArea.createSummary3LSA(0, 0, nextArea.stubDefaultCost);
          nextArea.installSelfOrigLSA(defaultLSA);
        } else {
          Summary_LSA defaultBody = (Summary_LSA)defaultLSA.payload();
          if (defaultBody.metric != nextArea.stubDefaultCost) {
	    nextArea.createSummary3LSA(0, 0, nextArea.stubDefaultCost);
            nextArea.installSelfOrigLSA(defaultLSA);
          }
        }
      }
    }

    // compute all INTER-AREA routes from the Summary_LSA Objects received from other routers.
    AreaData interArea = null;
    if (internal_rtr) { // router is connected to one area only!
      interArea = (AreaData)areaStructs.values().iterator().next();
    } else {
      interArea = (AreaData)areaStructs.get(new Integer(0));
    }
    if (interArea != null) {
      LSASlot nextSlot;
      LSA summaryHeader;
      for (Iterator it = interArea.database.summaryLSAs.values().iterator(); it.hasNext(); ) {
        nextSlot = (LSASlot)it.next();
        // RFC 2328 chapter 16.2. (2)
        if (nextSlot instanceof OwnLSASlot) continue;
        // RFC 2328 chapter 16.2. (1)
        summaryHeader = nextSlot.lsa;
        if (summaryHeader.payload() instanceof Summary_LSA) {
          Summary_LSA summaryBody = (Summary_LSA)summaryHeader.payload();
          if (summaryBody.metric == LS_INFINITY || summaryHeader.lsAge == MAX_AGE) continue;
          // RFC 2328 chapter 16.2. (4)
          RouterEntry advBorderRouter = routingTable.getDest(new Integer(summaryHeader.advRouter),
                                                             new Integer(interArea.areaID));
          if (advBorderRouter == null) continue; // no path to summary advertising border router!
          int newCost = advBorderRouter.cost + summaryBody.metric;
          // RFC 2328 chapter 16.2. (5)
          switch (summaryHeader.lsType) {
          case LSA.SUMMARY_NETWORK_LSA:
            String destination = (summaryHeader.lsID & summaryBody.netMask) + "/" + 
                                  summaryBody.netMask;
            NetworkEntry networkDest = routingTable.getDest(destination);
            if (networkDest == null || networkDest.pathType == TYPE_1_EXTERNAL || 
                networkDest.pathType == TYPE_2_EXTERNAL) {
              // RFC 2328 chapter 16.2. (5)
	      networkDest = new NetworkEntry(interArea.areaID, INTER_AREA, newCost, 0, null, 
                                             getEqualCostPaths(advBorderRouter.equalCostPaths,
                                                               summaryHeader.advRouter));
              // routing table update
              routingTable.add(networkDest, destination);
              continue;
            }
            // RFC 2328 chapter 16.2. (6)
            if (networkDest.pathType == INTRA_AREA) continue;
            // RFC 2328 chapter 16.2. (7)
            if (networkDest.pathType == INTER_AREA) {
              if (newCost < networkDest.cost) {
                // create new NetworkEntry
                networkDest = new NetworkEntry(interArea.areaID, INTER_AREA, newCost, 0, null,
                                               getEqualCostPaths(advBorderRouter.equalCostPaths,
                                                                 summaryHeader.advRouter));
                // routing table update
                routingTable.add(networkDest, destination);
                continue;
              }
              if (newCost == networkDest.cost) {
	        // add the shortest paths to the entry if necessary
	        ShortestPath sp1, sp2;
	        for (Enumeration sPaths = advBorderRouter.equalCostPaths.elements();
                     sPaths.hasMoreElements(); ) {
	          sp1 = (ShortestPath)sPaths.nextElement();
                  sp2 = new ShortestPath(summaryHeader.advRouter, sp1.nextHop);
                  if (!networkDest.equalCostPaths.contains(sp2)) networkDest.equalCostPaths.add(sp2);
                }
              }
            }
	  break;
          case LSA.SUMMARY_ASBR_LSA:
            RouterEntry routerDest = routingTable.getDest(new Integer(summaryHeader.lsID), 
                                                          new Integer(interArea.areaID));
            if (routerDest == null || routerDest.pathType == TYPE_1_EXTERNAL || 
                routerDest.pathType == TYPE_2_EXTERNAL) {
              // RFC 2328 chapter 16.2. (5)
	      routerDest = new RouterEntry(null, INTER_AREA, newCost, null, true,
                                           getEqualCostPaths(advBorderRouter.equalCostPaths,
                                                             summaryHeader.advRouter));
              routingTable.add(routerDest, new Integer(summaryHeader.lsID),
                               new Integer(interArea.areaID));
              continue;
            }
            // RFC 2328 chapter 16.2. (6)
            if (routerDest.pathType == INTRA_AREA) continue;
            // RFC 2328 chapter 16.2. (7)
            if (routerDest.pathType == INTER_AREA) {
              if (newCost < routerDest.cost) {
                routerDest = new RouterEntry(null, INTER_AREA, newCost, null, true,
                                             getEqualCostPaths(advBorderRouter.equalCostPaths,
                                                               summaryHeader.advRouter));
                routingTable.add(routerDest, new Integer(summaryHeader.lsID),
                                 new Integer(interArea.areaID));
                continue;
              }
              if (newCost == routerDest.cost) {
                // add the shortest paths to the entry if necessary
	        ShortestPath sp1, sp2;
                for (Enumeration sPaths = advBorderRouter.equalCostPaths.elements();
                     sPaths.hasMoreElements(); ) {
                  sp1 = (ShortestPath)sPaths.nextElement();
                  sp2 = new ShortestPath(summaryHeader.advRouter, sp1.nextHop);
                  if (!routerDest.equalCostPaths.contains(sp2)) routerDest.equalCostPaths.add(sp2);
                }
              }
            }
          break;
          }
        }
      }
    }

    // delete all net routes which have not become unreachable from the invalidated routing table 
    // update the ip table and, if this is an area border router generate summary LSAs if necessary.
    NetworkEntry nextEntry, oldEntry;
    boolean costChanged, pathChanged;
    Object[] routeKeys = routingTable.getNetKeys();
    for (int i=0; i < routeKeys.length; i++) {
      routeKey = (String)routeKeys[i];
      nextEntry = (NetworkEntry)routingTable.getDest(routeKey);
      oldEntry = (NetworkEntry)oldRoutingTable.getDest(routeKey);
      // compare the entrys and react respectively
      if (oldEntry == null) {
	// the route is really new
	rtcControlTimer.addIPRoute(routeKey, nextEntry);
        if (area_border_rtr && !(nextEntry.cost >= LS_INFINITY)) {
          originateSummary3LSA(nextEntry, routeKey);
        }
        continue;
      }
      costChanged = !oldEntry.equalCost(nextEntry);
      pathChanged = !oldEntry.sameShortestPaths(nextEntry);
      if (area_border_rtr && costChanged) {
	// originate an updated Summary 3 LSA...
        originateSummary3LSA(nextEntry, routeKey);
        // ...and possibly delete the old instance from the routing domain
        switch (oldEntry.pathType) {
	case INTRA_AREA:
	  if (oldEntry.area != 0 && nextEntry.area == 0) {
	    removeArea = (AreaData)areaStructs.get(new Integer(0));
	    removeArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, routeKey);
	  } else {
	    if (nextEntry.pathType == INTRA_AREA && nextEntry.area != 0 &&
                nextEntry.area != oldEntry.area) {
              removeArea = (AreaData)areaStructs.get(new Integer(nextEntry.area));
              removeArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, routeKey);
            }
	  }
	break;
        case INTER_AREA:
	  if (oldEntry.area == 0 && nextEntry.area != 0) {
            removeArea = (AreaData)areaStructs.get(new Integer(nextEntry.area));
            removeArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, routeKey);
          }
	break;
        default:
	break;
        }
      }
      // Anyway, if there is a change, update the ip table.
      if (costChanged || pathChanged) {
        rtcControlTimer.remIPRoute(routeKey);
        rtcControlTimer.addIPRoute(routeKey, nextEntry);
      }
      oldRoutingTable.delNet(routeKey);
    }

    // if this is an area border router, delete now all AS boundary routes which have not become 
    // unreachable from the invalidated routing table and generate summary LSAs if necessary.
    if (area_border_rtr) {
      RouterEntry nextRouter, oldRouter;
      Integer prefAreaID, oldAreaID;
      for (Iterator destKeys = routingTable.getRoutKeys(); destKeys.hasNext(); ) {
        routDest = (Integer)destKeys.next();
        prefAreaID = routingTable.getPrefASBREntryArea(routDest);
        // if the current route doesn't led to an AS boundary router, inspect the next one.
        if (prefAreaID.intValue() == -1) continue;
        nextRouter = (RouterEntry)routingTable.getDest(routDest, prefAreaID);
        // get the entry from invalid routing table that describes the former preferred path.
        oldAreaID = oldRoutingTable.getPrefASBREntryArea(routDest);
        // if there was no entry, create one.
        if (oldAreaID.intValue() == -1) {
          if (nextRouter.cost < LS_INFINITY) 
            originateSummary4LSA(prefAreaID, routDest, nextRouter.cost, nextRouter.pathType);
        } else {
	  // compare the cost of the old entry with the new one.
	  oldRouter = (RouterEntry)oldRoutingTable.getDest(routDest, oldAreaID);
	  if (nextRouter.cost != oldRouter.cost) {
            // originate an updated Summary 4 LSA...
	    originateSummary4LSA(prefAreaID, routDest, nextRouter.cost, nextRouter.pathType);
            // ...and possibly delete the old instance from the routing domain
            int newArea = prefAreaID.intValue();
            int oldArea = oldAreaID.intValue();
            switch (oldRouter.pathType) {
            case INTRA_AREA:
	      if ((oldArea != 0 || newArea != 0) && newArea != oldArea) {
                removeArea = (AreaData)areaStructs.get(new Integer(newArea));
                removeArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routDest);
	      }
	    break;
            case INTER_AREA: 
              if (newArea != 0 && newArea != oldArea) {
                removeArea = (AreaData)areaStructs.get(new Integer(newArea));
                removeArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routDest);
              }
	    break;
            default:
	    break;
            }
          }
        }
        oldRoutingTable.delRouter(routDest);
      }
      
      // premature age the summary LSAs of unreachable AS boundary routes.
      for (Iterator destKeys = oldRoutingTable.getRoutKeys(); destKeys.hasNext(); ) {
        routDest = (Integer)destKeys.next();
        oldAreaID = oldRoutingTable.getPrefASBREntryArea(routDest);
        // if the current route doesn't led to an AS boundary router, inspect the next one.
        if (oldAreaID.intValue() == -1) continue;
        // else remove the Summary LSA which has lead to the entry from certain areas.
        oldRouter = (RouterEntry)oldRoutingTable.getDest(routDest, oldAreaID);
        switch (oldRouter.pathType) {
        case INTRA_AREA:
          for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
            nextArea = (AreaData)itAreas.next();
            if (nextArea.areaID != oldAreaID.intValue() && nextArea.options[6])
	      nextArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routDest);
	  }
	break;
        case INTER_AREA:
          for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
            nextArea = (AreaData)itAreas.next();
            if (nextArea.areaID != oldAreaID.intValue() && nextArea.areaID != 0 && nextArea.options[6])
	      nextArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routDest);
	  }
	break;
        }
      }
    }

    // remove unreachable network routes from the IP table and premature age their corresponding
    // summary LSAs if any.
    routeKeys = oldRoutingTable.getNetKeys();
    for (int i=0; i < routeKeys.length; i++) {
      routeKey = (String)routeKeys[i];
      if (area_border_rtr) {
        oldEntry = oldRoutingTable.getDest(routeKey);
        switch (oldEntry.pathType) {
        case INTRA_AREA:
          for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
            nextArea = (AreaData)itAreas.next();
            if (nextArea.areaID != oldEntry.area)
	      nextArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, routeKey);
	  }
        break;
        case INTER_AREA:
          for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
            nextArea = (AreaData)itAreas.next();
            if (nextArea.areaID != oldEntry.area && nextArea.areaID != 0)
              nextArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, routeKey);
	  }
	break;
        }
      }
      rtcControlTimer.remIPRoute(routeKey);
    }
    return SSF.Net.Net.seconds(0.00000247*java.lang.Math.pow(x, 2.0) + 0.000978);
  }

  // ------------------------- OSPF.originateSummary3LSA --------------------------- //
  /** This method is called by area-border routers only, to check whether a new summary type 3
   *  must be originated. If so it creates, installs and floods a new summary LSA type 3 in
   *  the appropriate areas according to the parameters.
   *  @param netEntry NetworkEntry which causes the summary to be originated.
   *  @param destination String, representing the identifyer of the network to be announced.
   */
  public void originateSummary3LSA(NetworkEntry netEntry, String destination) {
    // check whether a Summary LSA must be created.
    AreaData nextAreaStruct;
    LSA newSummaryLSA;
    String[] destIDs = destination.split("/");
    switch (netEntry.pathType) {
    case INTRA_AREA:
      for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
        nextAreaStruct = (AreaData)itAreas.next();
        if (nextAreaStruct.areaID != netEntry.area) {
          newSummaryLSA = nextAreaStruct.createSummary3LSA(Integer.parseInt(destIDs[0]), 
                                          Integer.parseInt(destIDs[1]), netEntry.cost);
          if (netEntry.cost >= LS_INFINITY) newSummaryLSA.lsAge = MAX_AGE;
          nextAreaStruct.installSelfOrigLSA(newSummaryLSA);
        }
      }
    break;
    case INTER_AREA:
      for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
	nextAreaStruct = (AreaData)itAreas.next();
        if (nextAreaStruct.areaID != netEntry.area && nextAreaStruct.areaID != 0) {
          newSummaryLSA = nextAreaStruct.createSummary3LSA(Integer.parseInt(destIDs[0]), 
                                          Integer.parseInt(destIDs[1]), netEntry.cost);
          if (netEntry.cost >= LS_INFINITY) newSummaryLSA.lsAge = MAX_AGE;
          nextAreaStruct.installSelfOrigLSA(newSummaryLSA);
        }
      }
    break;
    case TYPE_1_EXTERNAL:
    case TYPE_2_EXTERNAL:
    break;
    }
  }

  // -------------------------- OSPF.originateSummary4LSA ----------------------------- //
  /** This method is called by area-border routers only. It checks whether a new summary 
   *  type 4 LSA must be originated. If so it creates, installs and floods a new summary 
   *  LSA type 4 in the appropriate areas according to the parameters (Summary 4 LSAs are
   *  not installed or flooded into stub areas!
   *  @param areaID Integer Object representing the area's id from which the AS Boundary
   *                route was learned.
   *  @param dest Integer Object indicating the AS Boundary routers router id.
   *  @param cost int value indicating the cost of the route.
   *  @param pathType byte value indicating the type of the path leading to the ASBR. 
   */
  public void originateSummary4LSA(Integer areaID, Integer dest, int cost, byte pathType) {
    AreaData nextArea;
    int aID = areaID.intValue();
    LSA newSummaryLSA;
    switch (pathType) {
    case INTRA_AREA:
      for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
        nextArea = (AreaData)itAreas.next();
        if (nextArea.areaID != aID && nextArea.options[6]) {
          newSummaryLSA = nextArea.createSummary4LSA(dest.intValue(), cost);
          if (cost >= LS_INFINITY) newSummaryLSA.lsAge = MAX_AGE;
          nextArea.installSelfOrigLSA(newSummaryLSA);
        }
      }
    break;
    case INTER_AREA:
      for (Iterator itAreas = areaStructs.values().iterator(); itAreas.hasNext(); ) {
	nextArea = (AreaData)itAreas.next();
        if (nextArea.areaID != aID && nextArea.areaID != 0 && nextArea.options[6]) {
          newSummaryLSA = nextArea.createSummary4LSA(dest.intValue(), cost);
          if (cost >= LS_INFINITY) newSummaryLSA.lsAge = MAX_AGE;
          nextArea.installSelfOrigLSA(newSummaryLSA);
        }
      }
    break;
    case TYPE_1_EXTERNAL:
    case TYPE_2_EXTERNAL:
    break;
    }
  }

  // ----------------------------- OSPF.buildIARoute ------------------------------ //
  /** This method is called, if the best route to a particular inter-area destination
   *  must be recalculated. The destination is specified by the given Summary_LSA and
   *  the LSA's associated area id.
   *  @param summaryHeader LSA just installed discribing a new inter area route.
   *  @param areaID int value, indicating the area from which the summary was obtained.
   *  @return long value, indicating the time in clock ticks that it took to build the
   *          inter-area route. Always 0 at the moment.
   */
  public long buildIARoute(LSA summaryHeader, int areaID) {
    Summary_LSA summaryBody = (Summary_LSA)summaryHeader.payload();
    // RFC 2328 chapter 16.2. (1)
    if (summaryBody.metric == LS_INFINITY || summaryHeader.lsAge == MAX_AGE) return 0;
    // RFC 2328 chapter 16.2. (4)
    RouterEntry advBorderRouter = routingTable.getDest(new Integer(summaryHeader.advRouter),
                                                       new Integer(areaID));
    if (advBorderRouter == null) return 0; // no path to summary advertising border router!
    int newCost = advBorderRouter.cost + summaryBody.metric;
    // RFC 2328 chapter 16.2. (5)
    switch (summaryHeader.lsType) {
    case LSA.SUMMARY_NETWORK_LSA:
      String destination = (summaryHeader.lsID & summaryBody.netMask) + "/" + summaryBody.netMask;
      NetworkEntry networkDest = routingTable.getDest(destination);
      if (networkDest == null) {
        // there has NOT been a route to that destination before.
        networkDest = new NetworkEntry(areaID, INTER_AREA, newCost, 0, null, 
                      getEqualCostPaths(advBorderRouter.equalCostPaths, summaryHeader.advRouter));
        // routing and ip table update
        routingTable.add(networkDest, destination);
        rtcControlTimer.addIPRoute(destination, networkDest);
        if (area_border_rtr) originateSummary3LSA(networkDest, destination);
      } else {
        // there ALREADY has been a route before.
        switch (networkDest.pathType) {
        case INTRA_AREA:
          // RFC 2328 chapter 16.2. (6)
          return 0;
          //break;
        case INTER_AREA:
          // RFC 2328 chapter 16.2. (7)
	  NetworkEntry newNetEntry;
          AreaData removeArea;
          if (newCost < networkDest.cost) {
            // create new NetworkEntry
            newNetEntry = new NetworkEntry(areaID, INTER_AREA, newCost, 0, null,
                          getEqualCostPaths(advBorderRouter.equalCostPaths, summaryHeader.advRouter));
            // routing and ip table update
            routingTable.add(newNetEntry, destination);
            rtcControlTimer.remIPRoute(destination);
            rtcControlTimer.addIPRoute(destination, newNetEntry);
            if (area_border_rtr) {
	      // Originate and remove some Summary3-LSAs
              originateSummary3LSA(newNetEntry, destination);
              if (networkDest.area == 0 && newNetEntry.area != 0) {
                removeArea = (AreaData)areaStructs.get(new Integer(areaID));
                removeArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, destination);
              }
            }
            break;
          }
          if (newCost > networkDest.cost) {
            ShortestPath nextElement;
            for (int i=0; i<networkDest.equalCostPaths.size(); ) {
              // remove all next hops if any, added due to "summaryHeader"
              nextElement = (ShortestPath)networkDest.equalCostPaths.get(i);
              if (nextElement.advRouter == summaryHeader.advRouter) 
                networkDest.equalCostPaths.remove(i);
              else 
                i++;
            }
            if (networkDest.equalCostPaths.isEmpty()) {
              // create new NetworkEntry
              newNetEntry = new NetworkEntry(areaID, INTER_AREA, newCost, 0, null,
                                             getEqualCostPaths(advBorderRouter.equalCostPaths, 
                                                               summaryHeader.advRouter));
              routingTable.add(newNetEntry, destination);
              if (area_border_rtr) {
	        // Originate and remove some Summary3-LSAs
                originateSummary3LSA(newNetEntry, destination);
                if (networkDest.area == 0 && newNetEntry.area != 0) {
                  removeArea = (AreaData)areaStructs.get(new Integer(areaID));
                  removeArea.removeSummaryLSA(LSA.SUMMARY_NETWORK_LSA, destination);
	        }
              }
	    }
            rtcControlTimer.remIPRoute(destination);
            rtcControlTimer.addIPRoute(destination, networkDest);
            break;
          }
          if (newCost == networkDest.cost) {
            // add the shortest paths to the entry if necessary
            ShortestPath sp1, sp2;
            for (Enumeration sPaths = advBorderRouter.equalCostPaths.elements();
                 sPaths.hasMoreElements(); ) {
              sp1 = (ShortestPath)sPaths.nextElement();
              sp2 = new ShortestPath(summaryHeader.advRouter, sp1.nextHop);
              if (!networkDest.equalCostPaths.contains(sp2)) networkDest.equalCostPaths.add(sp2);
            }
            // ip table update
            rtcControlTimer.remIPRoute(destination);
            rtcControlTimer.addIPRoute(destination, networkDest);
          }
        break;
        case TYPE_1_EXTERNAL:
        case TYPE_2_EXTERNAL:
          // RFC 2328 chapter 16.2. (5) second part
          networkDest = new NetworkEntry(areaID, INTER_AREA, newCost, 0, null, 
                        getEqualCostPaths(advBorderRouter.equalCostPaths, summaryHeader.advRouter));
          // routing and ip table update
          routingTable.add(networkDest, destination);
          rtcControlTimer.remIPRoute(destination);
          rtcControlTimer.addIPRoute(destination, networkDest);
          if (area_border_rtr) originateSummary3LSA(networkDest, destination);
          // TODO: remove the summary of pathType 1 or 2 EXTERNAL from the former area if its id has
          //       changed
        break;
        }
      }
    break;
    case LSA.SUMMARY_ASBR_LSA:
      Integer routerKey = new Integer(summaryHeader.lsID);
      Integer prefAreaID = routingTable.getPrefASBREntryArea(routerKey);
      RouterEntry newRouterEntry;
      if (prefAreaID.intValue() == -1) {
	// there is no entry for that ASBR.
        newRouterEntry = new RouterEntry(null, INTER_AREA, newCost, null, true,
                         getEqualCostPaths(advBorderRouter.equalCostPaths, summaryHeader.advRouter));
        routingTable.add(newRouterEntry, routerKey, new Integer(areaID));
        if (area_border_rtr) originateSummary4LSA(new Integer(areaID), routerKey, newRouterEntry.cost,
                                                  newRouterEntry.pathType);
      } else {
	  // there is a preferred RouterEntry.
        RouterEntry oldRouterEntry = routingTable.getDest(routerKey, prefAreaID);
        switch (oldRouterEntry.pathType) {
        case INTRA_AREA:
          // RFC 2328 chapter 16.2. (6)
	  return 0;
	  //break;
        case INTER_AREA:
          // RFC 2328 chapter 16.2. (7)
	  AreaData removeArea;
          if (newCost < oldRouterEntry.cost) {
            newRouterEntry = new RouterEntry(null, INTER_AREA, newCost, null, true,
                                             getEqualCostPaths(advBorderRouter.equalCostPaths, 
                                             summaryHeader.advRouter));
            routingTable.add(newRouterEntry, routerKey, new Integer(areaID));
            if (area_border_rtr) {
	      // Originate and remove some Summary4-LSAs
              originateSummary4LSA(new Integer(areaID), routerKey, newCost, newRouterEntry.pathType);
              if (prefAreaID.intValue() == 0 && areaID != 0) {
                removeArea = (AreaData)areaStructs.get(new Integer(areaID));
                removeArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routerKey);
              }
            }
            break;
          }
          if (newCost > oldRouterEntry.cost) {
            ShortestPath nextElement;
            for (int i=0; i<oldRouterEntry.equalCostPaths.size(); ) {
              // remove all next hops if any, added due to "summaryHeader"
              nextElement = (ShortestPath)oldRouterEntry.equalCostPaths.get(i);
              if (nextElement.advRouter == summaryHeader.advRouter) 
                oldRouterEntry.equalCostPaths.remove(i);
              else 
                i++;
            }
            if (oldRouterEntry.equalCostPaths.isEmpty()) {
	      // create new RouterEntry
	      newRouterEntry = new RouterEntry(null, INTER_AREA, newCost, null, true,
                                               getEqualCostPaths(advBorderRouter.equalCostPaths, 
                                               summaryHeader.advRouter));
              routingTable.add(newRouterEntry, routerKey, new Integer(areaID));
              if (area_border_rtr) {
	        // Originate and remove some Summary4-LSAs
                originateSummary4LSA(new Integer(areaID), routerKey, newCost, newRouterEntry.pathType);
                if (prefAreaID.intValue() == 0 && areaID != 0) {
                  removeArea = (AreaData)areaStructs.get(new Integer(areaID));
                  removeArea.removeSummaryLSA(LSA.SUMMARY_ASBR_LSA, routerKey);
                }
              }
            }
            break;
          }
          if (newCost == oldRouterEntry.cost) {
            // add the shortest paths to the entry if necessary
	    ShortestPath sp1, sp2;
            for (Enumeration sPaths = advBorderRouter.equalCostPaths.elements(); 
                 sPaths.hasMoreElements(); ) {
              sp1 = (ShortestPath)sPaths.nextElement();
              sp2 = new ShortestPath(summaryHeader.advRouter, sp1.nextHop);
              if (!oldRouterEntry.equalCostPaths.contains(sp2)) oldRouterEntry.equalCostPaths.add(sp2);
            }
          }
        break;
        case TYPE_1_EXTERNAL:
        case TYPE_2_EXTERNAL:
	  // RFC 2328 chapter 16.2 (5) second part
          newRouterEntry = new RouterEntry(null, INTER_AREA, newCost, null, true,
                           getEqualCostPaths(advBorderRouter.equalCostPaths, summaryHeader.advRouter));
          routingTable.add(newRouterEntry, routerKey, new Integer(areaID));
          if (area_border_rtr) originateSummary4LSA(new Integer(areaID), routerKey, newCost,
                                                    newRouterEntry.pathType);
          // TODO: remove the summary of pathType 1 or 2 EXTERNAL from the former area if its id has
          //       changed
	break;
        }
      }
    break;
    }
    return 0;
  }

  // -------------------------- OSPF.getEqualCostPaths ----------------------------- //
  /** Aid method for method "buildIARoute()" and "calculateRT()" to construct a new Vector 
   *  of shortest paths of equal cost.
   *  @param paths Vector containing ShortestPath objects from the advertising area border
   *               router.
   *  @param advRouter int value, indicating the router id of the router who is advertising
   *                   the ShortestPath objects.
   *  @return Vector containing the ShortestPath objects complemented with the value of 
   *          "advRouter". 
   */
  private Vector getEqualCostPaths(Vector paths, int advRouter) {
    Vector equalPaths = new Vector();
    ShortestPath sp1, sp2;
    for (Enumeration e = paths.elements(); e.hasMoreElements(); ) {
      sp1 = (ShortestPath)e.nextElement();
      sp2 = new ShortestPath(advRouter, sp1.nextHop);
      equalPaths.add(sp2);
    }
    return equalPaths;
  }

  // ---------------------------- OSPF.removeIARoute ------------------------------ //
  /** This method is called if a Summary LSA has/has reached MAX_AGE. In this case it
   *  must be determined, if "summaryHeader" has lead to an inter-area route. If so,
   *  the route must be deleted, otherwise nothing is done.
   *  @param summaryHeader Summary LSA which has MAX_AGE.
   *  @param areaID int value, indicating the area from which the summary was obtained.
   *  @return long value, indicating the time in clock ticks that it took to remove the
   *          inter-area route. Always 0 at the moment.
   */
  public long removeIARoute(LSA summaryHeader, int areaID) {
    RouterEntry advBorderRouter;
    int pathCost;
    ShortestPath nextElement;
    Summary_LSA summaryBody = (Summary_LSA)summaryHeader.payload();
    switch (summaryHeader.lsType) {
    case LSA.SUMMARY_NETWORK_LSA:
      String destination = (summaryHeader.lsID & summaryBody.netMask) + "/" + summaryBody.netMask;
      NetworkEntry networkDest = routingTable.getDest(destination);
      if (networkDest == null) return 0;
      if (networkDest.pathType != INTER_AREA) return 0;
      if (areaID != networkDest.area) return 0;
      advBorderRouter = routingTable.getDest(new Integer(summaryHeader.advRouter),new Integer(areaID));
      if (advBorderRouter == null) {
	printGeneralWarning("Inter-area route to network " + destination + 
                            " exists, but there is no route to the advertising border router!");
        return 0;
      }
      pathCost = advBorderRouter.cost + summaryBody.metric;
      if (networkDest.cost != pathCost) return 0;
      // at this point, the Summary LSA must be responsible for the entry.
      for (int i=0; i<networkDest.equalCostPaths.size(); ) {
	// remove all next hops, added due to "summaryHeader"
	nextElement = (ShortestPath)networkDest.equalCostPaths.get(i);
	if (nextElement.advRouter == summaryHeader.advRouter) networkDest.equalCostPaths.remove(i);
        else i++;
      }
      rtcControlTimer.remIPRoute(destination);
      if (networkDest.equalCostPaths.isEmpty()) {
        routingTable.delNet(destination);
        if (area_border_rtr) {
	  // remove the corresponding Summary-LSA from all other areas.
	  AreaData removeArea;
	  for (Iterator areas = areaStructs.values().iterator(); areas.hasNext(); ) {
	    removeArea = (AreaData)areas.next();
            if (removeArea.areaID != 0) removeArea.removeSummaryLSA(summaryHeader.lsType, destination);
          }
        }
      } else {
        rtcControlTimer.addIPRoute(destination, networkDest);
      }
    break;
    case LSA.SUMMARY_ASBR_LSA:
      RouterEntry routerDest = routingTable.getDest(new Integer(summaryHeader.lsID),
                                                    new Integer(areaID));
      if (routerDest == null) return 0;
      if (routerDest.pathType != INTER_AREA) return 0;
      advBorderRouter = routingTable.getDest(new Integer(summaryHeader.advRouter),new Integer(areaID));
      if (advBorderRouter == null) {
	printGeneralWarning("Inter-area route to router " + summaryHeader.lsID + 
                            " exists, but there is no route to the advertising border router!");
        return 0;
      }
      pathCost = advBorderRouter.cost + summaryBody.metric;
      if (routerDest.cost != pathCost) return 0;
      // at this point, the Summary LSA must be responsible for the entry.
      for (int i=0; i<routerDest.equalCostPaths.size(); ) {
	// remove all next hops, added due to "summaryHeader"
	nextElement = (ShortestPath)routerDest.equalCostPaths.get(i);
	if (nextElement.advRouter == summaryHeader.advRouter) routerDest.equalCostPaths.remove(i);
        else i++;
      }
      if (routerDest.equalCostPaths.isEmpty()) {
        routingTable.delRout(new Integer(summaryHeader.lsID), new Integer(areaID));
        if (area_border_rtr) {
	  // remove the corresponding Summary-LSA from all other areas.
	  AreaData removeArea;
	  for (Iterator areas = areaStructs.values().iterator(); areas.hasNext(); ) {
	    removeArea = (AreaData)areas.next();
            if (removeArea.areaID != 0 && removeArea.options[6]) 
              removeArea.database.prematureAgeLSA(summaryHeader.lsType, summaryHeader.lsID);
          }
        }
      }
    break;
    }
    return 0;
  }

  // ------------------------- OSPF.convertRouteID --------------------------------- //
  /** Convert a destination id into the ip table format.
   *  @param destID String, representing a destination id in OSPFv2 format.
   *  @return String, representing a destination id in ip table format.
   */
  public String convertRouteID(String destID) {
    String[] destIDs;
    String ipDestID;

    destIDs = destID.split("/");
    ipDestID = IP_s.IPtoString(Integer.parseInt(destIDs[0])) + "/";
    if (destIDs[1].equals("0")) {
      ipDestID += destIDs[1];
    } else {
      ipDestID += 32 - (int)(Math.log(Double.parseDouble(destIDs[1]) * -1.0)/Math.log(2.0));
    }
    return ipDestID;
  }

  // ------------------------------- OSPFv2.routeAddedBy -------------------------------------- //
  /**
   * Notification that the named protocol has added a new entry to the
   * IP forwarding table on this host.
   *
   * @param rinfo         Information about the route added to the FIB.
   * @param protocolName  The name of the protocol that added the route.
   */
  public void routeAddedBy(RoutingInfo rinfo, String protocolName) {
    // At the moment we do nothing.
  }

  // ------------------------------- OSPFv2.routeDeletedBy ------------------------------------ //
  /**
   * Notification that the named protocol has removed an entry from the
   * forwarding table on this host.
   *
   * @param rinfo         Information about the route deleted from the FIB.
   * @param protocolName  The name of the protocol that deleted the route.
   */
  public void routeDeletedBy(RoutingInfo rinfo, String protocolName) {
    // At the moment we do nothing.  Eventually, we will make sure that any
    // changes that could affect inter-domain routing are taken into account.
    // In particular, if the protocol is "iface" it means that an interface
    // went down.  If the protocol is "static" it means that a static route was
    // withdrawn.
  }

  // --------------------------- OSPF.oneOfAreas -------------------------- //
  /**
   * Returns true if this router belongs to the given area.
   */
  private boolean oneOfAreas(int area) {
    if (areaStructs.containsKey(new Integer(area)))
      return true;
    return false;
  }

  // ---------------------------- OSPF.printMessage ------------------------ //
  /** 
   * Method for printing debug Messages. 
   */
  public void printMessage (String message) {
    System.out.print(getSimTime() + "|OSPF Router " + nh + " ("+IP_s.IPtoString(rtrid)+")|");
    System.out.println(message + "\n");
  }

  // ------------------------- OSPF.printConfiguration --------------------- //
  /** print the router's current ospf configuration if necessary. That means if 
   *  configuration has changed since last print and one of the following ospf 
   *  options are set: "show_config", "show_all".
   */
  public void printConfiguration() {
    if ((debugOptions[9] == true || debugOptions[8] == true) && configChanged) {
      printMessage("Configuration done!" + "\n" + routerType +
                   "\nAS number: " + as_num +
                   "\naging interval: " + agingInterval/SSF.Net.Net.seconds(1.0) +
                   "\nshow_all: " + (new Boolean(debugOptions[9])).toString() +
                   "\nshow_lsdb: " + (new Boolean(debugOptions[0])).toString() +
                   "\nshow_fwd_table: " + (new Boolean(debugOptions[1])).toString() +
                   "\nshow_hello_pkt: " + (new Boolean(debugOptions[2])).toString() +
                   "\nshow_dd_pkt: " + (new Boolean(debugOptions[3])).toString() +
                   "\nshow_flooding: " + (new Boolean(debugOptions[4])).toString() +
                   "\nshow_area: " + (new Boolean(debugOptions[5])).toString() +
                   "\nshow_neighbors: " + (new Boolean(debugOptions[6])).toString() +
                   "\nshow_interfaces: " + (new Boolean(debugOptions[7])).toString() +
                   "\nshow_config: " + (new Boolean(debugOptions[8])).toString());
      configChanged = false;
    }
  }

  // - - - - - - - - - - - - - - error handling methods - - - - - - - - - - //

  // --------------------------- OSPF.printError ---------------------- //
  /**
   * Used by an OSPF Interface or Area to print error messages.
   */
  public void printError(String rise, String riseID, String errorStr) {
    String str = rise + " Error!!! " + rise + " " + riseID + " of OSPF Router " + nh + ": " 
                 + errorStr + "\n";
    System.err.println(str);
  }

  // --------------------------- OSPF.printConfigError --------------------------------- //
  /**
   * Used to print error messages concerning configuration of interfaces or areas. The
   * System will be halted immediately.
   * @param rise String representing "Area" or "Interface", depending on the class where
   *             the error arised.
   * @param riseID String representing the ip address/area id of the interface/area where
   *               the error arised.
   * @param riseEx exception rised by the error.
   */
  public void printConfigError(String rise, String riseID, Exception riseEx) {
    String str = "Error while configuring " + rise + " " + riseID + " of OSPF Router "+nh+": "
                 + riseEx.getMessage() + "\n";
    System.err.println(str);
    riseEx.printStackTrace();
    System.exit(-1);
  }

  public void printConfigError(String struct, int structID, String error) {
    String str = "Error while configuring " + struct + " " + structID + " of OSPF Router "+nh+": "
                 + error + "\n";
    System.err.println(str);
    System.exit(-1);
  }

  // --------------------------- OSPF.printGeneralError ------------------------------- //
  /** Used to print error messages in case of general errors. The system will be halted
   *  immediately.
   */
  public void printGeneralError(String occurence, Exception reason) {
    String str = "OSPFv2 on Router " + nh + " detected an error while " + occurence + "!\n" + 
                 reason.getMessage() + "\n";
    System.err.println(str);
    reason.printStackTrace();
    System.exit(-1);
  }

  // --------------------------- OSPF.printGeneralWarning ------------------------------ //
  /** Used to print warning messages when the system reports a general warning. */
  public void printGeneralWarning(String warning) {
    String str = "Warning from OSPFv2 on Router " + nh + "!\n" + warning;
    System.err.println(str);
  }

  // =========================== inner class AgingTimer ================================ //
  /**
   * A cyclic timer used to age all LSAs currently installed in the databases of this 
   * session and (for the future) all external LSAs stored here. Every time the timer fires
   * the LSAs are aged and all selforiginated LSAs are checked whether they have to be 
   * reoriginated and if so they are.
   */

  public class AgingTimer extends SSF.OS.Timer {

    // A link to the OSPF session this timer belongs.
    OSPF myOSPF;

    /** Construct a timer with the given duration. */
    public AgingTimer(OSPF myOSPF, long duration) {
      super(myOSPF.inGraph(), duration);
      this.myOSPF = myOSPF;
    }

    /** A method to be performed when the timer expires. It starts the aging in all link 
     *  state databases of its OSPF session and sees to it that selforiginated LSAs which
     *  must be reoriginated are send again. 
     */
    public void callback() {
      // age here the AS external LSAs.
      for (Iterator areas = areaStructs.values().iterator(); areas.hasNext();) {
	// now age all LSAs installed in the databases.
	((AreaData)areas.next()).database.ageAndCheckLSAs();
      }
      this.set(myOSPF.agingInterval);
    }
  } // end inner class AgingTimer

  // =========================== inner class RTCControlTimer ================================ //
  /**
   * The Routing Table Calculation Control Timer. This Timer is used to manage the routing table
   * calculation process. It delays the spf calculation when a topology change was received 
   * by a neighboring router due to configured spf_delay and spf_hold_time attributes and it
   * models routing table calculation delays.
   */

  public class RTCControlTimer extends SSF.OS.Timer {

    /** boolean value, indicating that a routing table recalculation is in progress and a new 
     *  calculation must be delayed. */
    private boolean rtIsProtected;
    /** boolean value, indicating that an ip table update must be performed. */
    private boolean doFIBUpdate;
    /** boolean value indicating an entire recalculation of the OSPF/IP table. */
    private boolean entireCalculation;
    /** boolean value, indicating that a routing table recalculation is in progress but meanwhile
     *  a new update has arrived, forcing a recalculation. */
    private boolean createNewRT;
    /** boolean value, indicating the last time when a recalculation has taken place. */
    long lastCalculationTime;
    HashMap removeSummaries, installSummaries, installIpRoutes;
    HashSet removeIpRoutes;
    /** number of clock-ticks to calculate spf and to update the ip table. */
    long rtCalculationDelay, ipUpdateDelay;

    /** Construct a timer with the given duration. */
    public RTCControlTimer(OSPF myOSPF, long duration) {
      super(myOSPF.inGraph(), duration);
      rtIsProtected = false;
      doFIBUpdate = false;
      entireCalculation = false;
      createNewRT = false;
      lastCalculationTime = inGraph().now() - spfHoldTime;
      removeSummaries = new HashMap();
      installSummaries = new HashMap();
      removeIpRoutes = new HashSet();
      installIpRoutes = new HashMap();
    }

    /** method to be performed when the entire RT must be recalculated. */
    public void buildRoutingTable() {
      if (!entireCalculation) {
        entireCalculation = true;
        removeSummaries.clear();
        installSummaries.clear();
      }
      if (rtIsProtected) {
	createNewRT = true;
      } else {
        if (spfDelay == 0 && spfHoldTime == 0) {
	  rtIsProtected = true;
          doFIBUpdate = true;
	  rtCalculationDelay = calculateRT();
          this.set(rtCalculationDelay);
          entireCalculation = false;
        } else {
          if (this.isCancelled()) {
	    // spf_delay or spf_hold_time are configured
	    if (spfDelay != 0) {
              this.set(spfDelay);
            } else {
              long difference = inGraph().now() - lastCalculationTime;
              if (difference < spfHoldTime) {
                this.set(spfHoldTime - difference);
              } else {
                lastCalculationTime = inGraph().now();
                rtIsProtected = true;
                doFIBUpdate = true;
	        rtCalculationDelay = calculateRT();
                this.set(rtCalculationDelay);
                entireCalculation = false;
              }
            }
          }
        }
      }
    }

    /** When the time is reached to calculate the RT and only an inter-area route part must
     *  be recalculated this method is called to execute the calculation. */
    private long calculateIARoutes() {
      int areaID;
      long delay = 0;
      Object[] keys;
      String nextKey;
      LSA removeLSA, installLSA;

      if (internal_rtr) {
        areaID = ((Integer)areaStructs.keySet().iterator().next()).intValue();
      } else {
        if (area_border_rtr) areaID = 0;
        else return 0;
      }
      // process all Summary LSAs with age MAX_AGE
      keys = removeSummaries.keySet().toArray();
      for (int i=0; i < keys.length; i++) {
	nextKey = (String)keys[i];
        removeLSA = (LSA)removeSummaries.get(nextKey);
        if (installSummaries.containsKey(nextKey)) {
          installLSA = (LSA)installSummaries.get(nextKey);
          if (removeLSA.lsSeqnum > installLSA.lsSeqnum) {
	    installSummaries.remove(nextKey);
          }
        }
        delay += removeIARoute(removeLSA, areaID);
      }
      for (Iterator insts = installSummaries.values().iterator(); insts.hasNext(); ) {
	installLSA = (LSA)insts.next();
        delay += buildIARoute(installLSA, areaID);
      }
      removeSummaries.clear();
      installSummaries.clear();
      return (delay);
    }

    /** Aid method for "callback()". It is called when a RT calculation has just finished and
     *  an inter-area routes part must again be recalculated.
     */
    private void buildInterAreaRoutes() {
      LSA nextSummary;
      int areaID;

      if (spfDelay == 0 && spfHoldTime == 0) {
	rtIsProtected = true;
	rtCalculationDelay = calculateIARoutes();
        ipUpdateDelay =  SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
        this.set(rtCalculationDelay + ipUpdateDelay);
      } else {
        if (this.isCancelled()) {
	  // spf_delay or spf_hold_time are configured
	  if (spfDelay != 0) {
            this.set(spfDelay);
          } else {
            long difference = inGraph().now() - lastCalculationTime;
            if (difference < spfHoldTime) {
              this.set(spfHoldTime - difference);
            } else {
              lastCalculationTime = inGraph().now();
              rtIsProtected = true;
	      rtCalculationDelay = calculateIARoutes();
              ipUpdateDelay =  SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
              this.set(rtCalculationDelay + ipUpdateDelay);
            }
          }
        }
      }
    }

    /** Stores a route which must be installed in the ip table.
     *  @param destination String containing the address of the destination.
     *  @param netEntry NetworkEntry keeping the information neccessary to install the route in
     *                  the ip table.
     */
    public void addIPRoute(String destination, NetworkEntry netEntry) {
      installIpRoutes.put(convertRouteID(destination), netEntry);
    }

    /** Stores a route which must be removed from the ip table.
     *  @param destination String containing the address of the destination.
     */
    public void remIPRoute(String destination) {
      removeIpRoutes.add(convertRouteID(destination));
    }

    /** Called when a received Summary LSA has changed. It makes a decision to remove or to
     *  build an inter-area route and schedules its execution time.
     *  @param summaryLSA LSA of type summary which has changed.
     *  @param areaID int value indicating the area where the LSA came from.
     */
    public void processInterAreaRoute(LSA summaryLSA, int areaID) {
      boolean remove;
      if (entireCalculation || !((area_border_rtr && areaID == 0) || internal_rtr)) return;
      remove = (summaryLSA.lsAge == MAX_AGE);

      if (rtIsProtected) {
	createNewRT = true;
      } else {
        if (spfDelay == 0 && spfHoldTime == 0) {
	  rtIsProtected = true;
          if (remove) rtCalculationDelay = removeIARoute(summaryLSA, areaID);
          else rtCalculationDelay = buildIARoute(summaryLSA, areaID);
          ipUpdateDelay =  SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
          this.set(rtCalculationDelay + ipUpdateDelay);
          return;
        } else {
	  if (this.isCancelled()) {
            if (spfDelay != 0) {
              this.set(spfDelay);
            } else {
              long difference = inGraph().now() - lastCalculationTime;
              if (difference < spfHoldTime) {
                this.set(spfHoldTime - difference);
              } else {
                lastCalculationTime = inGraph().now();
		rtIsProtected = true;
                if (remove) rtCalculationDelay = removeIARoute(summaryLSA, areaID);
                else rtCalculationDelay = buildIARoute(summaryLSA, areaID);
                ipUpdateDelay =  SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
                this.set(rtCalculationDelay + ipUpdateDelay);
                return;
              }
            }
          }
        }
      }
      if (remove) removeSummaries.put(summaryLSA.getKey(), summaryLSA);
      else installSummaries.put(summaryLSA.getKey(), summaryLSA);
    }

    /** reset this timer */
    public void reset() {
      if (rtIsProtected) {
	createNewRT = false;
        this.callback();
      } else {
        rtIsProtected = false;
        removeIpRoutes.clear();
        installIpRoutes.clear();
        this.cancel();
      }
      entireCalculation = false;
      removeSummaries.clear();
      installSummaries.clear();
      lastCalculationTime = inGraph().now() - spfHoldTime;
    }

    /** A method to be performed when the timer expires. It manages the Routing Table calculation
     *  process by using some flag-bits which indicate the current phase of the calculation
     *  (rtIsProtected, doFIBUpdate, entireCalculation, createNewRT) and the time values "spfDelay"
     *  and "spfHoldTime".
     */
    public void callback() {
      if (rtIsProtected) {
	// routing table was recalculated. 
	if (doFIBUpdate) {
	  // IP update is still to do, but first print out now the shortest path tree if necessary.
          if (debugOptions[9] || debugOptions[10]) {
            String output;
            AreaData nextArea;
            Dijkstra.Vertex node;
            for (Iterator areas = areaStructs.values().iterator(); areas.hasNext(); ) {
	      nextArea = (AreaData)areas.next();
              printMessage("|Shortest Path Tree for Area " + nextArea.areaID + 
                           " recalculated! Graph topology is now:");
              for (Iterator i = nextArea.shortestPathTree.values().iterator(); i.hasNext(); ) {
                node = (Dijkstra.Vertex)i.next();
                System.out.println("node: " + IP_s.IPtoString(node.id) + " cost: " + node.dist);
                output = "   predecessor(s): ";
                for (Enumeration e = node.predecessors.elements(); e.hasMoreElements(); ) {
	          output += IP_s.IPtoString(((Dijkstra.Vertex)e.nextElement()).id) + " ";
                }
                System.out.println(output);
                output = "   outgoing interface(s): ";
                if (!node.nextHops.isEmpty()) {
                  for (Enumeration e = node.nextHops.elements(); e.hasMoreElements(); ) {
	            output += IP_s.IPtoString(((NextHop)e.nextElement()).viaInterface) + " ";
                  }
                } else {
	          output += "none ";
                }
                System.out.println(output);
              }
            }
            System.out.println();
          }
          doFIBUpdate = false;
          // calculate ip table update delay and set the timer.
          ipUpdateDelay = SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
          this.set(ipUpdateDelay);
          return;
	}
	// Now update the IP table.
	Object[] ipRoutes = installIpRoutes.keySet().toArray();
        String nextIpDest;
        NetworkEntry nextNetEntry;
        NIC viaInterface;
        ShortestPath sp;
	for (Iterator remIPs = removeIpRoutes.iterator(); remIPs.hasNext(); ) {
	  nextIpDest = (String)remIPs.next();
          ipTable.del(nextIpDest, "ospf");
        }
        for (int i=0; i < ipRoutes.length; i++) {
	  nextIpDest = (String)ipRoutes[i];
          nextNetEntry = (NetworkEntry)installIpRoutes.get(nextIpDest);
          for (Enumeration e = nextNetEntry.equalCostPaths.elements(); e.hasMoreElements(); ) {
            sp = (ShortestPath)e.nextElement();
            viaInterface = (NIC)rtr.interfaceAddresses.get(new Integer(sp.getViaIntf()));
            ipTable.add(nextIpDest, viaInterface, sp.getNHopIP(), nextNetEntry.cost, "ospf");
          }
        }
        removeIpRoutes.clear();
        installIpRoutes.clear();
        rtIsProtected = false;
        // RT calculation has terminated, if necessary inform about LS Database modifications...
        for (Iterator areas = areaStructs.values().iterator(); areas.hasNext(); ) {
          ((AreaData)areas.next()).databaseDebugInfo();
        }
        if (debugOptions[9] == true || debugOptions[1] == true) routingTable.print();
        this.cancel();
        if (createNewRT) {
	  createNewRT = false;
	  if (entireCalculation) buildRoutingTable();
          else buildInterAreaRoutes();
        }
      } else {
        // spf_delay or spf_hold_time has expired
        if (spfHoldTime != 0) {
          // check whether spf_hold_time has expired
          long difference = inGraph().now() - lastCalculationTime;
          if (difference < spfHoldTime) {
            this.set(spfHoldTime - difference);
            return;
          } else {
	    lastCalculationTime = inGraph().now();
          }
        }
        // No hold time configured or hold time expired.
        rtIsProtected = true;
        if (entireCalculation) {
	  // calculate the entire routing table
	  doFIBUpdate = true;
	  rtCalculationDelay = calculateRT();
          this.set(rtCalculationDelay);
          entireCalculation = false;
        } else {
	  // calculate the changes to the routing table caused by summary LSAs
	  rtCalculationDelay = calculateIARoutes();
          ipUpdateDelay = SSF.Net.Net.seconds(0.001*((int)(randomStream.nextDouble() * 200.0) + 100));
          this.set(rtCalculationDelay + ipUpdateDelay);
        }
      }
    }
  } // end inner class RTCControlTimer


} // end class OSPF
